import argparse
from typing import Callable, Dict, Iterable, List, NoReturn, Optional, Tuple, Union

from sty import bg

from res import (Base, Board, Event, EventTypes, Mountain, Player, River, Tree,
                 Warrior, Wood, numeral_input, random_board, readchar,
                 to_fullwidth)

_sel_pos = None
_sel_entity = None
_players = None
person = None
board = None


def _c_blink_callback(c: int):
    _end_blink([(c2, r) for c2 in range(board._width)
                for r in range(board._height)])
    _start_blink([(c, r) for r in range(board._height)])
    print('\x1b[s\x1b[0;0H', end='', flush=True)
    board.print()
    print('\x1b[u', end='', flush=True)


def _r_blink_callback(i: int, c: int):
    _end_blink([(i2, j) for i2 in range(board._width)
                for j in range(board._height)])
    _start_blink([(c, i)])
    print('\x1b[s\x1b[0;0H', end='', flush=True)
    board.print()
    print('\x1b[u', end='', flush=True)


def _select_pos(must: bool = True, blink: bool = True):
    if not blink:
        c_callback = None
        r_callback = None
    else:
        c_callback = _c_blink_callback
        r_callback = lambda i: _r_blink_callback(i, c)
    c = -1
    while c == -1 or (must and c < 0):
        print('\r\x1b[KEnter the column number [0-%i]: ' %
              (board._width - 1), end='', flush=True)
        c = numeral_input(0, board._width, callback=c_callback)
    if c == -2:
        return (-1, -1)
    c = int(c)
    r = -1
    while r == -1 or (must and r < 0):
        print('\rEnter the row number [0-%i]: ' %
              (board._height - 1), end='', flush=True)
        r = numeral_input(0, board._height, callback=r_callback)
    if r == -2:
        return (-1, -1)
    return (c, r)


def _start_blink(poses: Iterable[Tuple[int, int]]):
    for pos in poses:
        board[pos[0]][pos[1]].blink = True


def _end_blink(poses: Iterable[Tuple[int, int]]):
    for pos in poses:
        board[pos[0]][pos[1]].blink = False


def select_pos():
    c = -1
    while c == -1:
        # c = input('\x1b[1A\x1b[KEnter the column number [0-%i]: ' %
        #   (board._width - 1))
        print('\r\x1b[KEnter the column number [0-%i]: ' %
              (board._width - 1), end='', flush=True)
        c = numeral_input(0, board._width,
                          callback=lambda i: _end_blink(
                              [(i2, j) for i2 in range(board._width)
                               for j in range(board._height)]
                          ) or _start_blink([(i, j) for j in range(board._height)])
                          or print('\x1b[s\x1b[0;0H', end='', flush=True) or board.print()
                          or print('\x1b[u', end='', flush=True))
    if c == -2:
        return True
    c = int(c)
    r = -1
    while r == -1:
        # r = input('\x1b[1A\x1b[KEnter the row number [0-%i]: ' %
        #   (board._height - 1))
        print('\rEnter the row number [0-%i]: ' %
              (board._height - 1), end='', flush=True)
        r = numeral_input(0, board._height,
                          callback=lambda i: _end_blink(
                              [(i2, j) for i2 in range(board._width)
                               for j in range(board._height)]
                          ) or _start_blink([(c, i)])
                          or print('\x1b[s\x1b[0;0H', end='', flush=True) or board.print()
                          or print('\x1b[u', end='', flush=True))
    if r == -2:
        return True
    print('Selected position: (%i, %i)' % (c, r))
    _end_blink([(c, r)])
    global _sel_pos, _sel_entity
    _sel_pos = (c, r)
    _sel_entity = None
    return True


def next_person():
    global _sel_pos, _sel_entity, person
    _sel_pos = None
    _sel_entity = None
    person = _players[0 if person == _players[-1]
                      else (_players.index(person) + 1)]


def select_entity():
    entities = board[_sel_pos[0]][_sel_pos[1]].entities
    if not entities:
        print('There are no entities here!')
        return True
    c = 'a'
    for i in range(len(entities)):
        print('%i. %s' % (i + 1, entities[i].__class__.__name__))
    while not c.isdigit() or c == '0' or int(c) > len(entities):
        c = input('\r\x1b[KPlease choose a number: ')
    c = int(c)
    global _sel_entity
    _sel_entity = entities[c - 1]


def print_pos():
    ev = Event(EventTypes.print_pos, pos=_sel_pos)
    board[_sel_pos[0]][_sel_pos[1]].event_trigger(ev)
    print('\n'.join(ev.print))
    return True


# def attack():


structure: Dict[str, Tuple[int, Callable[[], Optional[bool]], Union[bool, Callable[[], bool]]]] = {
    # 'Name': (
    #     which_letter_to_highlight_index,
    #     the_function_to_call_when_chosen,
    #     when_to_display_this_function_or_bool
    # )
    'Quit': (0, lambda: print('Goodbye!\n') + exit(), True),
    'Continue': (0, next_person, True),
    'Select a position': (0, select_pos, lambda: _sel_pos is None),
    'Select another position': (0, select_pos, lambda: _sel_pos is not None),
    'Print current position': (0, print_pos, lambda: _sel_pos is not None),
    'sElect an entity': (1, select_entity, lambda: _sel_entity is None and _sel_pos is not None),
    'sElect another entity': (1, select_entity, lambda: _sel_entity is not None and _sel_pos is not None),
    # 'Attack': (0, attack, lambda: _sel_entity.__class__ in (Warrior,))
}


def parse_pos() -> Tuple[str, Dict[str, Callable]]:
    ret = 'Player ' + person.name + ':\n'
    if _sel_pos is not None:
        ret += 'Selected position: ' + repr(_sel_pos) + '\n'
    if _sel_entity is not None:
        ret += 'Selected entity: ' + _sel_entity.__class__.__name__ + '\n'
    choices = {}
    for pos in structure:
        ok = structure[pos][2]
        if callable(ok):
            ok = ok()
        if ok:
            ret += 'Press '
            ret += pos[structure[pos][0]]
            ret += ' to '
            ret += pos.lower()
            ret += '\n'
            choices[pos[structure[pos][0]].upper()] = structure[pos][1]
            # choices[pos[structure[pos][0]].lower()] = structure[pos][1]
    return (ret, choices)


def main(players: List[str], width=20, height=20):
    global board, person, _players
    _players = players
    for i in range(len(players)):
        players[i] = Player(players[i])
    board = Board(random_board(((River, 0.1), (Mountain, 0.1),
                                (Tree, 0.03), (Wood, 0.015)), width, height))
    print('\x1b[2J\x1b[0;0H', end='')
    board.print()
    COLORS = {
        '0': bg.da_cyan,
        '1': bg.li_magenta,
        '2': bg.li_red,
        '3': bg.da_magenta,
        '4': bg.da_blue,
        '5': bg.da_green
    }
    for p in players:
        c = 'a'
        print('\x1b[2J\x1b[0;0H', end='')
        board.print()
        print('Hello player %s! Please choose your base color:' % p.name)
        for c in COLORS:
            print('  %s. %s%s%s' %
                  (c, COLORS[c], to_fullwidth(' '), bg.rs))
        print('Please choose your color [%s]: ' %
              ''.join(COLORS), end='', flush=True)
        c = readchar(''.join(COLORS))
        print()
        if c == '\x03':
            exit(1)
        p.color = COLORS[c]
        del COLORS[c]
    for p in players:
        b = Base(p)
        print('\x1b[2J\x1b[0;0H', end='')
        board.print()
        print('Player %s, please select your base position:' % p.name)
        while True:
            c = -1
            while c < 0:
                print('\rEnter the column number [0-%i]: ' %
                      (board._width - 1), end='', flush=True)
                c = numeral_input(0, board._width, q=False)
            r = -1
            while r < 0:
                print('\rEnter the row number [0-%i]: ' %
                      (board._height - 1), end='', flush=True)
                r = numeral_input(0, board._height, q=False)
            if not board[c][r].place_building(b):
                print('This position is not good!')
            else:
                break
        board[c][r].append(b)
    person = players[0]
    while True:
        print('\x1b[2J\x1b[0;0H', end='')
        board.print()
        print()
        p = parse_pos()
        print(p[0], 'Choice [', ''.join(p[1]), ']: ',
              end='', sep='', flush=True)
        c = readchar(p[1])
        print()
        if c == '\x03':
            print('Goodbye!\n')
            exit(1)
        if p[1][c]():
            print('Press any key to continue... ', end='', flush=True)
            r = readchar()
            if r == '\x03':
                print('Goodbye!\n')
                exit(1)


if __name__ == '__main__':
    ap = argparse.ArgumentParser(
        prog='board',
        description='an interesting board game',
        add_help=True,
        allow_abbrev=True
    )
    ap.add_argument(
        '-W', '--width',
        default=20,
        type=int,
        required=False,
        help='The width of the game board.',
        metavar='<integer>',
        dest='width'
    )
    ap.add_argument(
        '-H', '--height',
        default=20,
        type=int,
        required=False,
        help='The height of the game board.',
        metavar='<integer>',
        dest='height'
    )
    ap.add_argument(
        '-p', '--player',
        action='append',
        # nargs='+',
        type=str,
        required=True,
        help='The name of a player playing the game. Use this option'
        'multiple times for multiple players.',
        metavar='<string>',
        dest='players'
    )
    args = ap.parse_args()
    main(args.players, args.width, args.height)
